很多遊戲引擎都使用ECS架構,所以也想寫寫看。但如果只了解理論就要從0開始時做我來真的不會,畢竟我就爛。不過還好有這篇的幫助,才讓我順利的寫(抄?)完ㄌ,之後有時間在寫寫看屬於自己的ECS架構吧XD。
前面說過我們將遊戲物件及其行為拆成以下三個東西
每個東西還有每個東西的Manager
,所以寫起來其實是很麻煩的。
Entity僅是一個簡單的ID
,他不會真的存著Component
或任何其他東西,事實上他只會用來作為Component
陣列的索引而已
using Entity = std::uint32_t;
const Entity MAX_ENTITES = 10000;
Component
只會存著需要的Data
,所以寫起來也很簡單
struct Transform {
float x_;
float y_;
};
struct Graphic {
sf::Texture_;
sf::Sprite_;
}
每種Component
也會需要一個ID
。
using ComponentType = std::uint8_t;
const ComponentType MAX_COMPONENT = 32;
我們希望夠能追蹤這個Entity
擁有哪些Component
,同時也必須追蹤這個System
會有哪些Component
參與其中。
為了方便追蹤,我們會給Entity
及System
一個Signature
,上面登記著它擁有了哪些Component
。
std::bitset
很適合達到我們的要求,前面我們幫每種Component
訂了ID
,意思是指要此物件或系統擁有這物件,就只要設定那個ID
的bit
。
舉個例子,Transform
是0
,Graphic
是1
,RigidBody
是2
,
擁有Transform
和RigidBody
的物件,它的Signature
會被設定成0b101
(bit 0, 2)會被設定。
Entity Manager
是負責分配及追蹤Entity
,記錄著哪些Entity ID
已經被使用
用最簡單的std::queue
就可以達成,當產生一個Entity
,我們就返回一個Entity ID
,當Entity
被銷毀,我們再將此ID
push
回去就好了
class EntityManager {
public:
EntityManager();
Entity createEntity();
void destroyEntity(Entity entity);
void setSignature(Entity entity, Signature signature);
Signature getSignature(Entity entity);
private:
// Array of signatures where the index is the Entity
std::array<Signature, MAX_ENTITIES> Signatures_;
// ALL Unused Entities
std::queue<Entity> Entites_;
// Total living Entites
uint32_t EntitiesCounts_ = 0;
};
EntityManager::EntityManager() {
// Unused Entites
for(Entity entity = 0 ; entity < MAX_ENTITIES ; entity++)
Entites_.push(entity);
}
Entity EntityManager::createEntity() {
assert(EntitiesCounts_ < MAX_ENTITIES && "Too Many Entities");
Entity id = Entites_.front();
Entites_.pop();
++EntitiesCounts_;
return id;
}
void EntityManager::destroyEntity(Entity entity) {
assert(entity < MAX_ENTITIES && "Entity Out Of Range");
Signatures_[entity].reset();
Entites_.push(entity);
--EntitiesCounts_;
}
void EntityManager::setSignature(Entity entity, Signature signature) {
assert(entity < MAX_ENTITIES && "Entity Out Of Range");
Signatures_[entity] = signature;
}
Signature EntityManager::getSignature(Entity entity) {
assert(entity < MAX_ENTITIES && "Entity Out Of Range");
return Signatures_[entity];
}
在這裡,我們必須實作一種簡單的陣列,但必須永遠是連續的,代表說它是不會有洞出現的。
因為我們的Entity
只是ID
,要獲得跟Entity
相關的Component
是很簡單的,但是當Entity
被銷毀時,對於陣列來說此Index
(Entity ID
)已經失效,我們不希望在遍歷陣列時出現失效的Entity
。
但是事實上當Entity
被銷毀時,它仍然是存在在陣列裡的。
在這裡,我們維護了兩個map
,存著Entity ID
與Index
之間的關係。當你要使用Entity
時,你利用Entity ID
去尋找在陣列裡實際的位置。當Entity
被移除時,我們將陣列裡最後一個尚未失效的Entity
移動到被移除的位置上,接著只要更新map
,一切就完成。
在這裡我先掩飾給大家看看。
我們假設MAX_ENTITES
5,陣列一開始是空的,map
也是空的
Array | 0: | 1: | 2: | 3: | 4: | 5: |
---|---|---|---|---|---|---|
Entity->Index | ||||||
Index->Entity | ||||||
Size | 0 |
接著我們加入Entity A
, 它的Entity ID
為0
在陣列中的位置是0,所以將map
的對應關係寫好
Array | 0: A | 1: | 2: | 3: | 4: |
---|---|---|---|---|---|
Entity->Index | 0:0 | ||||
Index->Entity | 0:0 | ||||
Size | 1 |
接著加入B
Array | 0: A | 1: B | 2: | 3: | 4: |
---|---|---|---|---|---|
Entity->Index | 0:0 | 1:1 | |||
Index->Entity | 0:0 | 1:1 | |||
Size | 2 |
加入C
Array | 0: A | 1: B | 2: C | 3: | 4: |
---|---|---|---|---|---|
Entity->Index | 0:0 | 1:1 | 2:2 | ||
Index->Entity | 0:0 | 1:1 | 2:2 | ||
Size | 3 |
加入D
Array | 0: A | 1: B | 2: C | 3: D | 4: |
---|---|---|---|---|---|
Entity->Index | 0:0 | 1:1 | 2:2 | 3:3 | |
Index->Entity | 0:0 | 1:1 | 2:2 | 3:3 | |
Size | 4 |
到這裡都很好,陣列保持連續,但接著我們要刪除B,也就是Entity 1
,為了保持連續,我們將D覆蓋B的位置然後更新map
刪除B
Array | 0: A | 1: D | 2: C | 3: | 4: |
---|---|---|---|---|---|
Entity->Index | 0:0 | 3:1 | 2:2 | ||
Index->Entity | 0:0 | 1:3 | 2:2 | ||
Size | 3 |
接著刪除Entity 3
,也就是D
Array | 0: A | 1: C | 2: | 3: | 4: |
---|---|---|---|---|---|
Entity->Index | 0:0 | 2:1 | |||
Index->Entity | 0:0 | 1:2 | |||
Size | 2 |
然後我們加入E,也就是Entity 4
Array | 0: A | 1: C | 2: E | 3: | 4: |
---|---|---|---|---|---|
Entity->Index | 0:0 | 2:1 | 4:2 | ||
Index->Entity | 0:0 | 1:2 | 2:4 | ||
Size | 3 |
這樣陣列就保持連續了!!!
class IComponentArray {
public:
virtual ~IComponentArray() = default;
virtual void entityDestroyed(Entity entity) = 0;
};
template<typename T>
class ComponentArray: public IComponentArray {
public:
void insertData(Entity entity, T component) {
// assert(EntityToIndex.find(entity) == EntityToIndex.end() && "Components added to the same Entity more than once");
size_t newIndex = size_;
EntityToIndex[entity] = newIndex;
IndexToEntity[newIndex] = entity;
componentArray_[newIndex] = component;
++size_;
}
void removeData(Entity entity) {
// assert(EntityToIndex.find(entity) != EntityToIndex.end() && "Entity does not exist");
size_t remove = EntityToIndex[entity];
size_t lastElement = size_-1;
componentArray_[remove] = componentArray_[lastElement];
Entity lastEntity = IndexToEntity[lastElement];
EntityToIndex[lastEntity] = remove;
IndexToEntity[remove] = lastEntity;
EntityToIndex.erase(entity);
IndexToEntity.erase(lastElement);
--size_;
}
T& getComponent(Entity entity) {
// assert(EntityToIndex.find(entity) != EntityToIndex.end() && "retrieving none exist component");
return componentArray_[EntityToIndex[entity]];
}
void entityDestroyed(Entity entity) override {
if(EntityToIndex.find(entity) != EntityToIndex.end())
removeData(entity);
}
private:
std::array<T, MAX_ENTITIES> componentArray_;
std::unordered_map<Entity, size_t> EntityToIndex;
std::unordered_map<size_t, Entity> IndexToEntity;
size_t size_ = 0;
};
這層抽象層是必要的,之後我們會有很多很多的Component array,並且用一個list存著他們,每當有Entity被銷毀時,我們必須逐一地去通知他們有Entity被銷毀了。能讓我們存各個不同type的Component的辦法就是有一層抽象層了。
當然一定有不用抽象層的方法,但這裡只是簡單實作,而且EntityDestroyed()也不是每個frame都會被呼叫。
Component Manager
是負責管理所有的Component Array
的,並不是讓他們之間溝通,而是通知他們我註冊了哪些Component
,或是該刪除哪些Component
class ComponentManager {
public:
template<typename T>
void registerComponent() {
const char* typeName = typeid(T).name();
assert(componentTypes_.find(typeName) == componentTypes_.end() && "Registering component type more than once");
componentTypes_.insert({typeName, nextComponentType_});
componentArray_.insert({typeName, std::make_shared<ComponentArray<T>>()});
++nextComponentType_;
}
template<typename T>
ComponentType getComponentType() {
const char* typeName = typeid(T).name();
assert(componentTypes_.find(typeName) != componentTypes_.end() && "Component did not register");
return componentTypes_[typeName];
}
template<typename T>
void addComponent(Entity entity, T component) {
getComponentArray<T>()->insertData(entity, component);
}
template<typename T>
void removeComponent(Entity entity) {
getComponentArray<T>()->removeData(entity);
}
template<typename T>
T& getComponent(Entity entity) {
return getComponentArray<T>()->getComponent(entity);
}
void entityDestroyed(Entity entity) {
for(auto const& pair: componentArray_) {
auto const& component = pair.second;
component->entityDestroyed(entity);
}
}
private:
std::unordered_map<const char*, ComponentType> componentTypes_;
std::unordered_map<const char*, std::shared_ptr<IComponentArray>> componentArray_;
ComponentType nextComponentType_ = 0;
template<typename T>
std::shared_ptr<ComponentArray<T>> getComponentArray() {
const char* typeName = typeid(T).name();
assert(componentTypes_.find(typeName) != componentTypes_.end() && "Component did not register");
return std::static_pointer_cast<ComponentArray<T>>(componentArray_[typeName]);
}
};
System
是行為存在的地方。
每個System
都放著存著Entity
的list
class System {
public:
std::set<Entity> Entities_;
};
你說行為呢?
當我們需要甚麼系統就繼承它
class PhysicSystem : public System {
public:
void update(float dt);
};
class GraphicSystem : public System {
public:
void update();
void render(sf::RenderTarget& target);
};
一個System
的upadte
一般長這樣
void PhysicSystem::update(float dt) {
for(auto const& entity : Entities_) {
auto& transform = ecs.getComponent<Transform>(entity);
auto& rigidBody = ecs.getComponent<RigidBody>(entity);
transform.y_ += rigidBody.v_ * dt;
rigidBody.v_ += 10 * dt;
}
}
System Manager
是負責管理所有的System
及他們的Signature
。
每個System
的Signature
代表此System
會用到的Component
,這樣才能將合適的Entity
分配給它。
當Entity
被銷毀,System
的list
也得跟著更新。
class SystemManager {
public:
template<typename T>
std::shared_ptr<T> registerSystem() {
const char* typeName = typeid(T).name();
assert(system_.find(typeName) == system_.end() && "Registering system more than once");
auto system = std::make_shared<T>();
system_.insert({typeName, system});
return system;
}
template<typename T>
void setSignature(Signature signature) {
const char* typeName = typeid(T).name();
assert(system_.find(typeName) != system_.end() && "System did not register");
signatures_.insert({typeName, signature});
}
void entityDestroyed(Entity entity) {
for(auto const& pair : system_) {
auto const& system = pair.second;
system->Entities_.erase(entity);
}
}
void entitySignatureChanged(Entity entity, Signature entitySignature) {
for(auto const& pair: system_){
auto const& type = pair.first;
auto const& system = pair.second;
auto const& systemSignature = signatures_[type];
if((entitySignature & systemSignature) == systemSignature) system->Entities_.insert(entity);
else system->Entities_.erase(entity);
}
}
private:
std::unordered_map<const char*, Signature> signatures_;
std::unordered_map<const char*, std::shared_ptr<System>> system_;
};
現在我們大致上的功能都有了,我們有管理Entity
的Entity Manager
。
管理Component
的Component Manager
。管理System
的System Manager
。
這三個Manager
必須互相溝通。
有很多種辦法 來處理這個問題,設定成gloabal
之類的,但我這裡打算寫個class
把它包起來
class ECS {
public:
ECS() = default;
void init();
Entity createEntity();
void destroyEntity(Entity entity);
template<typename T>
void registerComponent() {
componentManager_->registerComponent<T>();
}
template<typename T>
void addComponent(Entity entity, T component) {
componentManager_ ->addComponent<T>(entity, component);
auto signature = entityManager_->getSignature(entity);
signature.set(componentManager_->getComponentType<T>(), true);
entityManager_->setSignature(entity, signature);
systemManager_->entitySignatureChanged(entity, signature);\
}
template<typename T>
void removeComponent(Entity entity){
componentManager_->removeComponent<T>(entity);
auto signature = entityManager_->getSignature(entity);
signature.set(componentManager_->getComponentType<T>(), false);
entityManager_->setSignature(entity, signature);
systemManager_->entitySignatureChanged(entity, signature);
}
template<typename T>
T& getComponent(Entity entity) {
return componentManager_->getComponent<T>(entity);
}
template<typename T>
ComponentType getComponentType() {
return componentManager_->getComponentType<T>();
}
template<typename T>
std::shared_ptr<T> registerSystem() {
return systemManager_->registerSystem<T>();
}
template<typename T>
void setSystemSignature(Signature signature) {
systemManager_->setSignature<T>(signature);
}
private:
std::unique_ptr<EntityManager> entityManager_;
std::unique_ptr<ComponentManager> componentManager_;
std::unique_ptr<SystemManager> systemManager_;
};
void ECS::init() {
entityManager_ = std::make_unique<EntityManager>();
componentManager_ = std::make_unique<ComponentManager>();
systemManager_ = std::make_unique<SystemManager>();
std::cout << "ECS init\n";
}
Entity ECS::createEntity() {
return entityManager_->createEntity();
}
void ECS::destroyEntity(Entity entity) {
entityManager_->destroyEntity(entity);
componentManager_->entityDestroyed(entity);
systemManager_->entityDestroyed(entity);
}
一樣,為了讓成千上萬個沙奈朵掉下來,我們一樣有三個Component
struct Transform{
public:
float x_;
float y_;
};
struct RigidBody{
public:
float v_;
};
struct Graphic{
public:
Gardevoir *gardevoir_;
sf::Texture texture;
sf::Sprite sprite_;
};
Gardevoir的用處在Component Pattern有提到
然後我們要來寫我們的System
我們的Graphic System
class GraphicSystem : public System {
public:
void update();
void setSprite(Entity entity);
void render(sf::RenderTarget& target);
};
void GraphicSystem::update() {
for(auto const& entity: Entities_) {
auto& graphic = ecs.getComponent<Graphic>(entity);
auto& transform = ecs.getComponent<Transform>(entity);
graphic.sprite_.setPosition(transform.x_, transform.y_);
}
}
void GraphicSystem::render(sf::RenderTarget& target) {
for(auto const& entity : Entities_) {
auto& graphic = ecs.getComponent<Graphic>(entity);
target.draw(graphic.sprite_);
}
}
void GraphicSystem::setSprite(Entity entity) {
auto& graphic = ecs.getComponent<Graphic>(entity);
graphic.sprite_.setTexture(graphic.gardevoir_->texture);
graphic.sprite_.setScale(0.1, 0.1);
}
PhysicSystem
class PhysicSystem : public System {
public:
void update(float dt);
};
void PhysicSystem::update(float dt) {
for(auto const& entity : Entities_) {
auto& transform = ecs.getComponent<Transform>(entity);
auto& rigidBody = ecs.getComponent<RigidBody>(entity);
transform.y_ += rigidBody.v_ * dt;
rigidBody.v_ += 10 * dt;
}
}
接著我們的主程式我分開來講
首先我們初始化我們的ECS
,並註冊我們的Component
ECS::ECS ecs;
ecs.init();
ecs.registerComponent<ECS::Transform>();
ecs.registerComponent<ECS::RigidBody>();
ecs.registerComponent<ECS::Graphic>();
接著我們註冊我們的System
,記得設定Signature
auto graphicSystem = ecs.registerSystem<ECS::GraphicSystem>();
ECS::Signature signature;
signature.set(ecs.getComponentType<ECS::Graphic>());
signature.set(ecs.getComponentType<ECS::Transform>());
ecs.setSystemSignature<ECS::GraphicSystem>(signature);
auto physicSystem = ecs.registerSystem<ECS::PhysicSystem>();
signature.set(ecs.getComponentType<ECS::Transform>());
signature.set(ecs.getComponentType<ECS::RigidBody>());
ecs.setSystemSignature<ECS::PhysicSystem>(signature);
接著就可以開始產生我們的Entity
了
std::vector<ECS::Entity> entites(ECS::MAX_ENTITIES);
for(auto& entity: entites) {
entity = ecs.createEntity();
ecs.addComponent(
entity,
ECS::Transform{randPositionX(generator), randPositionY(generator), p}
);
ecs.addComponent(
entity,
ECS::RigidBody{20}
);
ecs.addComponent(
entity,
ECS::Graphic{gardevoir}
);
}
for(auto& entity: graphicSystem->Entities_) {
graphicSystem->setSprite(entity);
}
這裡利用我們的Graphic System幫我們設定圖片
接著就是Game Loop
了
while (running) {
auto startTime = std::chrono::high_resolution_clock::now();
sf::Event event;
while (window.pollEvent(event))
{
// Close window: exit
if (event.type == sf::Event::Closed)
running = false;
}
physicSystem->update(dt);
graphicSystem->update();
window.clear(bgColor);
graphicSystem->render(window);
// window.draw(s2);
window.display();
auto stopTime = std::chrono::high_resolution_clock::now();
dt = std::chrono::duration<float, std::chrono::seconds::period>(stopTime - startTime).count();
std::cout << dt << "\n";
}
這樣就大功告成啦~~~
出來的效果應該跟昨天的一喔。